---
title: Kubernetes library
sidebar:
  order: 6
---

import { FileTree } from '@astrojs/starlight/components';

The last section has shown that using a library for creating Kubernetes objects
can drastically simplify the code you need to write. However, there is a huge
amount of different kinds of objects and the Kubernetes API is evolving (and
thus changing) quite rapidly.

Writing and maintaining such a library could be a full-time job on it's own.
Luckily, it is possible to generate such a library from the Kubernetes OpenAPI
specification! Even better, it has already been done for you.

## k8s-libsonnet

The library is called `k8s-libsonnet` (replacing the discontinued `ksonnet-lib`),
currently available at https://github.com/jsonnet-libs/k8s-libsonnet.

:::note
The `ksonnet` project has been abandoned, the library is not maintained
anymore. However, the community backed by Grafana Labs has picked up on this with
the `k8s-libsonnet` library.
:::

As `k8s-libsonnet` has broken compatibility in a few places with `ksonnet-lib` (for good
reason), we have instrumented the widely used `ksonnet-util` library with a
compatibility layer to improve the developer and user experience:
https://github.com/grafana/jsonnet-libs/tree/master/ksonnet-util

If you do not have any strong reasons against it, just adopt the wrapper as
well, it will ease your work. Many of the original `ksonnet-util` enhancements
have already made their way into `k8s-libsonnet`.

The docs for `k8s-libsonnet` library can be found here:
https://jsonnet-libs.github.io/k8s-libsonnet/

## Installation

Like every other external library, `k8s-libsonnet` can be installed using
`jsonnet-bundler`.
However, Tanka already **did this for you** during [project
creation (`tk init`)](./tutorial/jsonnet/#creating-a-new-project):

```bash
tk init
jb install github.com/jsonnet-libs/k8s-libsonnet/1.21@main github.com/grafana/jsonnet-libs/ksonnet-util

```

This created the following structure in `/vendor`:

<FileTree>

- vendor
  - github.com
    - grafana
      - jsonnet-libs
        - ksonnet-util
          - kausal.libsonnet # Grafana's wrapper
          - ...
    - jsonnet-libs
      - k8s-libsonnet
        - 1.21
          - main.libsonnet # k8s-libsonnet entrypoint
          - ...
  - 1.21/ -> github.com/jsonnet-libs/k8s-libsonnet/1.21
  - ksonnet-util/ -> github.com/grafana/jsonnet-libs/ksonnet-util

</FileTree>

:::note[Info]
The `vendor/` is the location for external libraries, while `lib/`
can be used for your own ones. Check [import paths](./libraries/import-paths/)
for more information.
:::

#### Aliasing

Because of how `jb` works, the library can be imported as
`github.com/jsonnet-libs/k8s-libsonnet/1.21/main.libsonnet`. Most external
libraries (including our wrapper) expect it as a simple `k.libsonnet` (without
the package prefix).

To support both, Tanka automatically created an alias file for you:
`/lib/k.libsonnet` that just imports the actual library, exposing it under this
alternative name as well.

:::note[Info]
This works, because `import` behaves like copy-pasting. So the contents of
`k8s-libsonnet/1.21` are "copied" into our new file, making them behave exactly the
same.
:::

## Using it

First we need to import it in `main.jsonnet`:

```diff
- local k = import "kubernetes.libsonnet";
+ local k = import "github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet";
  local grafana = import "grafana.libsonnet";
  local prometheus = import "prometheus.libsonnet";
  { /* ... */ }
```

:::note
`ksonnet-util` imports literal `k.libsonnet`, so [aliasing](./tutorial/k-lib/#aliasing) is
a must here. This works, because `/lib` and `/vendor` are automatically searched
for libraries, and `k.libsonnet` can be found in `/lib` due to aforementioned
aliasing.
:::

Now that we have installed the correct version, let's use it in
`/environments/default/grafana.libsonnet` instead of our own helper:

```jsonnet
// /environments/default/grafana.libsonnet
local k = import "github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet";

{
  // use locals to extract the parts we need
  local deployment = k.apps.v1.deployment,
  local container = k.core.v1.container,
  local containerPort = k.core.v1.containerPort,
  local service = k.core.v1.service,
  // defining the objects:
  new(name, port):: {
    // deployment constructor: name, replicas, containers
    deployment: deployment.new(name, replicas=1, containers=[
      // container constructor
      container.new('grafana', 'grafana/grafana')
      + container.withPorts([ // add ports to the container
        containerPort.newNamed(name='ui', containerPort=3000),
      ]),
    ]),

    // instead of using a service constructor, our wrapper provides
    // a handy helper to automatically generate a service for a Deployment
    service: k.util.serviceFor(self.deployment)
             + service.mixin.spec.withType('NodePort'),
  },
}
```

## Full example

Now that creating the individual objects does not take more than 5 lines, we can
merge it all back into a single file (`main.jsonnet`) and take a look at the
whole picture:

```jsonnet
local k = import "github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet";

{
  local deployment = k.apps.v1.deployment,
  local container = k.core.v1.container,
  local port = k.core.v1.containerPort,
  local service = k.core.v1.service,

  prometheus: {
    deployment: deployment.new(
      name="prometheus", replicas=1,
      containers=[
        container.new("prometheus", "prom/prometheus")
        + container.withPorts([port.new("api", 9090)]),
      ],
    ),
    service: k.util.serviceFor(self.deployment),
  },
  grafana: {
    deployment: deployment.new(
      name="grafana", replicas=1,
      containers=[
        container.new("grafana", "grafana/grafana")
        + container.withPorts([port.new("ui", 3000)]),
      ],
    ),
    service:
      k.util.serviceFor(self.deployment)
      + service.mixin.spec.withType("NodePort"),
  },
}
```

That's a pretty big improvement, considering how verbose and error-prone it was
before!

## Bonus: Config object

While this is already a huge improvement, we can do a bit more. There is still some repetition in the `main.jsonnet` file.
The most straightforward way to address this is by creating a hidden object that holds all actual values in a single place to be consumed by the actual resources.

Luckily, Jsonnet has the `key:: "value"` stanza for private fields. Such are only available during compiling and will be removed from the actual output.

Such an object could look like this:

```jsonnet
{
  _config:: {
    grafana: {
      port: 3000,
      name: "grafana",
    },
    prometheus: {
      port: 9090,
      name: "prometheus"
    }
  }
}
```

We can then replace hardcoded values with a reference to this object:

```diff lang="jsonnet" ///.*/
local k = import "github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet";

{ // <- This is $
+  _config:: {
+    grafana: {
+      port: 3000,
+      name: "grafana",
+    },
+    prometheus: {
+      port: 9090,
+      name: "prometheus"
+    }
+  }

  local deployment = k.apps.v1.deployment,
  local container = k.core.v1.container,
  local port = k.core.v1.containerPort,
  local service = k.core.v1.service,

  prometheus: {
    deployment: deployment.new(
-      name="prometheus", replicas=1,
+      // $ refers to the outermost object
+      name=$._config.prometheus.name, replicas=1,
      containers=[
-        container.new("prometheus", "prom/prometheus")
-        + container.withPorts([port.new("api", 9090)]),
+        container.new($._config.prometheus.name, "prom/prometheus")
+        + container.withPorts([port.new("api", $._config.prometheus.port)]),
      ],
    ),
    service: k.util.serviceFor(self.deployment),
  },
  grafana: {
    deployment: deployment.new(
-       name="grafana", replicas=1,
+       name=$._config.grafana.name, replicas=1,
      containers=[
-        container.new("grafana", "grafana/grafana")
-        + container.withPorts([port.new("ui", 3000)]),
+        container.new($._config.grafana.name, "grafana/grafana")
+        + container.withPorts([port.new("ui", $._config.grafana.port)]),
      ],
    ),
    service:
      k.util.serviceFor(self.deployment)
      + service.mixin.spec.withType("NodePort"),
  },
}
```
